/**
 * Title:        Utility Class Library<p>
 * Description:  Some useful utility classes.<p>
 * @version 1.0
 */
package com.timber.util;

import java.util.*;
import java.text.ParseException;


/**
 * <code>Formatter</code> is an abstract base class for formatting
 * data representations into strings.
 *
 * <p>
 * This class provides similar functionality as the java.text.format
 * class, but will add necessary functionality such as input validation
 * and context completion (e.g. 5K => 5,000)
 *
 * @see StringFormatter
 * @see NumberFormatter
 * @see java.text.Format
 * @author
 *
 *
 */

public abstract class Formatter implements java.io.Serializable
{

  private static Hashtable instanceCache = null;

  public Formatter()
  {
  }

  /**
   * Formats an object to produce a string.
   * Subclasses will implement for particular object.
   */
  public abstract String format( Object o );

  /**
   * Parses a string to produce an object.
   * Subclasses will typically implement for particular object.
   */



  //	public abstract Class getDataType();


  /**
   * Validates the entered newChar that has been typed while editing the
   * formatted value.  By default, it will simply call validate() on the
   * newVal string.
   */
  public boolean accepts( String oldVal,
			  int oldPos,
			  char newChar,
			  String newVal,
			  int newPos )
  {
    if( newVal.length() == 0 )
      return true;
    else
      return validate( newVal );
  }

  /**
   * Validates the entered text by calling parseObject and checking to see if
   * a ParseException is thrown.
   */
  public boolean validate( String text )
  {
    try
    {
      parseObject( text );
    }
    catch( ParseException e )
    {
      return false;
    }
    return true;
  }


  /**
   * Allows an input to be completed from its context, usually the existing
   * value of what is being input. Used to increment (tick ) Prices or even
   * dates where the input text contains special values (like +2, +1d, -1m
   * for example)
   */
  public String completeInContext( String inputText, Object  contextValue )
  {
    return inputText;
  }

  /**
   * As above but the context is presented as a string.
   */
  public String completeInContext( String inputText, String oldText )
  {
    try
    {
      Object o = null;
      if( oldText != null && oldText.trim().length() != 0 )
	o = parseObject( oldText );
      return completeInContext( inputText, o );
    }
    catch( ParseException e )
    {
      return inputText;
    }
  }

  /**
   * This method allows formatters to rewrite the text as it is being entered. The parameters
   * are the same as for accepts().  It is only called if accepts() returns true. The default
   * implementation simply returns the supplied newText.
   * @return The contents the buffer should take after entering the character
   * @see #accepts
   */
  public String newText( String oldText,
			 int oldPos,
			 char newChar,
			 String newText,
			 int newPos )
  {
    return newText;
  }


  /**
   * returns the class of the datatype excepted by the formatter
   */
  public abstract Class getDataType();


  /**
   * Query whether this formatter can output super- and sub-scripts or not.
   * Returns false by default, but may be overridden by formatters such as
   * NumberFormatter if ticks are enabled. Used to optimise performance of
   * SimpleGrid and SimpleTextField.
   */
  public boolean hasSubscripts()
  {
    return false;
  }


  /**
   * Return an instance of Formatter depending on the format required, specified by a string.
   * Examples of valid formats are "N3" (3 d.p. number), "D" (date) and "S" (string).
   */
  public static Formatter instance( String f )
  {
    if( instanceCache == null )
      instanceCache = new Hashtable( 20 );

    // Cache number formatters for a given precision. 0 - 9;
    // We can reuse a number formatter for precision x again since
    // they are immutable.

    Formatter fmt = ( Formatter )instanceCache.get( f );
    if( fmt != null )
      return fmt;

    switch( f.charAt( 0 ) )
    {
      case 'N':
	fmt = new NumberFormatter( Integer.parseInt( f.substring( 1 ) ) );
	break;

      case 'K':
				// ticks, fractions of 32 by default
	fmt = new NumberFormatter( 0,
				   0,
				   true,
				   false,
				   f.length() > 1 ? Integer.parseInt( f.substring( 1 ) ) : 32 );
	break;

      case 'D':
	{
	  DateFormatter df = new DateFormatter();
	  fmt = df;

	  /*
	    if (f.length()>1 && f.charAt(1)=='X')
	    df.tickable = false;
	  */
	  break;
	}

      case 'T':
	{
	  DateFormatter df = new DateFormatter( DateFormatter.TIMEFMT );
	  fmt = df;
	  break;
	}

      case 'I':
	{
	  DateFormatter df = new DateFormatter( DateFormatter.DATETIMEFMT_INT_COMPARE );
	  fmt = df;
	  break;
	}


      case 'S':
	// can't cache because not immutable
	return new StringFormatter();

      default:
	throw new IllegalArgumentException( "Unsupported format " + f );
    }

    // Util.debug( "New Formatter: " + f );
    instanceCache.put( f, fmt );
    return fmt;
  }

  public abstract Object parseObject( String source ) throws ParseException;

  /**
   ** Formats an object obj of known class types to a string of size size
   **
   ** @param obj        an object of known class types: Double, Integer, String, Boolean
   ** @param size       the size of the resulting string
   **
   ** @return           String representation of obj, such that length() of the string equals to @size
   *#
   **/

  public static String formatObject2size(Object obj, int size) {
      String ret = null;

      if (obj == null) {
         StringBuffer fmt = new StringBuffer(size);
         for(int i=0;i<size;i++) fmt.append(' ');
         ret = fmt.toString();
      }
      else if (obj instanceof Double)
           ret = NumberFormatter.formatDouble((Double)obj, size, 3);
      else if (obj instanceof Integer)
           ret = NumberFormatter.formatInt((Integer)obj, size);
      else if (obj instanceof Boolean)
           ret = (((Boolean)obj).booleanValue()) ? "1" : "0";
      else if (obj instanceof String)
           ret = StringFormatter.formatStr((String)obj, size);
      else
           ret = StringFormatter.formatStr(obj.toString(), size);

      return ret;
  }

}
